import os
import re
import sys
import json
import shutil
import psutil
import subprocess
import urllib.request
from urllib.error  import URLError, HTTPError
from libs.variable import migrate_repostr
from libs.variable import services, config

''' Get variables values '''
log_dir = config["log_dir"]
yum_dir = config["yum_dir"]
data_dir = config["data_dir"]
work_dir = config["work_dir"]
install_log_file  = config["install_log_file"]

print("......  Please wait a few minutes for environment preparation before migration  ......")
def Requirements():
    print("[INFO]: Check whether the upgrade tool is executed by ROOT user ")
    if os.geteuid() != 0:
        print("[FAIL]: Please run the tool as root user")
        sys.exit(1)

    ''' Clean and make work dir '''
    print("[INFO]: Clean and make work dir")
    if os.path.isdir(work_dir):
        shutil.rmtree(work_dir)
        os.makedirs(data_dir)
        os.makedirs(log_dir)
    else:
        os.makedirs(data_dir)
        os.makedirs(log_dir)

    print("[INFO]: Check internal network environment")
    dst_url = "http://mirrors.opencloudos.tech/opencloudos/9.0/"
    try:
        net_status = urllib.request.urlopen(dst_url, timeout=10).code
    except (URLError, HTTPError) as err:
        print("[FAIL]: Check internal network environment is False")
        sys.exit(1)

    print("[INFO]: Make sure the disk is not read-only.")
    disk_partitions = psutil.disk_partitions()
    for partition in disk_partitions:
        if partition.opts.startswith('ro'):
            print("[FAIL]: Disk is readonly!")
            sys.exit(1)


def BaseTools():
    print("[INFO]: Check the System base tools")
    for basetools_pkg in ['rpm', 'yum']:
        base_ret = False
        paths = os.environ['PATH'].split(':')
        for cmd_path in paths:
            if not os.path.isdir(cmd_path):
                continue
            for f in os.listdir(cmd_path):
                cmd_all_path = os.path.join(cmd_path, f)
                if os.path.isfile(cmd_all_path):
                    if f == basetools_pkg:
                        base_ret = True
                        break
    if not base_ret:
        print(f"[FAIL]: Can not find the base tools of rpm or yum, please check!")
        sys.exit(1)
    return base_ret


    
def InstRpmsInfo(migration_time):
    json_file = ""
    rpmsinfo_file_path = ""
    print("[INFO]: Get the information of rpms installed in the system.")
    pkgs_raw = str(subprocess.check_output('rpm -qa --qf "%{NAME}|%{VERSION}|%{RELEASE}|%{INSTALLTIME}|%{VENDOR}|%{BUILDTIME}|%{SOURCERPM}|%{PACKAGER}\n"', shell=True), 'utf-8').strip()
    pkgs_list = [dict(zip(["name", "version", "release", "install_time", "vendor", "builddate", "sourcerpm", "packager"], line.split('|'))) for line in pkgs_raw.split('\n')]
    all_pkgs_dict = { package['name']: package for package in pkgs_list}
    all_pkgs_json = json.dumps(all_pkgs_dict, indent=4)
    ''' Rpmsinfo data is written to json file '''
    rpmsinfo_file_path = ""
    rpmsinfo_file_path = f"{data_dir}/migrate_{migration_time}_rpmsinfo.json"
    ''' Write all pacaages json  info ton file. '''
    WriteData(rpmsinfo_file_path, all_pkgs_json, mode="w")
    return all_pkgs_json



def InstRpmsName(migration_time):
    rpmsname_file_path = ""
    ''' Get the information of rpms name. '''
    print("[INFO]: Get the information of rpms name")
    all_pkgs_name = str(subprocess.check_output('rpm -qa --qf "%{NAME}\n" | sort -u | grep -v "^$"',shell=True),'utf-8').strip()
    ''' Rpms name data is written to file '''
    rpmsname_file_path = f"{data_dir}/migrate_{migration_time}_rpmsname.txt"
    '''  Write all packages names to file. '''
    WriteData(rpmsname_file_path, all_pkgs_name, mode="w")
    all_pkgs_name_set = set(all_pkgs_name.strip().split('\n'))
    return all_pkgs_name_set


def RpmQaV(migration_time):
    ''' Use command of "rpm -qaV"  to get Rpms safe info '''
    print(f"[INFO]: Use command of [rpm -qaV] to get rpms`s files changed info in migrate {migration_time}")
    result = subprocess.run("rpm -qaV", shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE,check=False)
    result_str = result.stdout.decode('utf-8')
    rpmV_file_path = ""
    rpmV_file_path = f"{data_dir}/migrate_{migration_time}_rpmV.txt"
    ''' Write all packages rpms file hanging info to file. '''
    WriteData(rpmV_file_path, result_str, mode="w")


def ServiceStatus(migration_time):
    service_name_path = ""
    ''' Get a working serveice info '''
    service_info = subprocess.check_output("systemctl list-units --type=service --state=running", shell=True).decode('utf-8').strip()
    service_name_list = [service_name.split()[0] for service_name in service_info.split('\n') if 'running' in service_name]
    service_name = '\n'.join(service_name_list)
    service_name_path = f"{data_dir}/migrate_{migration_time}_service_names.txt"
    ''' Write service info to file. '''
    WriteData(service_name_path, service_name, mode="w")
    return service_name


def RpmStatus(rpm):
    rpm_status = True
    rpm_result = subprocess.run(f"rpm -q {rpm} | grep oc9",shell=True)
    if rpm_result.returncode != 0:
        rpm_status = False
    return rpm_status


def YumStatus():
    try:
        subprocess.run("yum clean all;yum makecache", shell=True, check=True)
        return True
    except subprocess.CalledProcessError as e:
        return False


def InstRpms(*instrpms, opts="", prompt="n"):
    ''' Install Rpms '''
    instrpms_cmd = ""
    instrpms_cmd_msg = ""
    instrpms = ' '.join(instrpms)
    instopts = opts
    if instrpms == "distro-sync":
        instrpms_cmd = f"dnf -y {instrpms} {instopts}"
    else:
        instrpms_cmd = f"dnf install -y {instrpms} {instopts}"
    instrpms_cmd_msg = f"\n[CMD]: {instrpms_cmd_msg}"
    print(f"\n[CMD]: {instrpms_cmd}")
    process = subprocess.Popen(instrpms_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    ''' Show standard output '''
    stdout_lines = []
    stdout_lines.append(f"[CMD]: {instrpms_cmd}\n")
    for right_line in process.stdout:
         stdout_line = right_line.decode('utf-8')
         print(stdout_line, end='')
         stdout_lines.append(stdout_line)
    sys.stdout.flush()
    stdout_str = ' '.join(stdout_lines)
    ''' Write install log to file '''
    WriteData(install_log_file, stdout_str ,mode="a")

    ''' Show standard Err output '''
    stderr_lines = []
    for err_line in process.stderr:
        stderr_line = err_line.decode('utf-8')
        print(stderr_line, end='')
        stderr_lines.append(stderr_line)
    sys.stdout.flush()
    process.wait()
    stderr_str = ' '.join(stderr_lines)
    ''' Write install log to file '''
    WriteData(install_log_file, stderr_str ,mode="a")

    ''' Prompt and guide for installation failure. '''
    if process.returncode != 0 and prompt == "y":
        InstFailMsgFilter(stderr_str)
    return process.returncode


def InstFailMsgFilter(stderr_str_info):
    ''' Give some suggestions on solutions to the installation failure. '''
    pattern = r"(\s+file\s+(\S+)\s+from install of\s+(\S+)\s+conflicts with file from package\s+(\S+))|(No match for argument:\s+(\S+))|(No package\s+(\S+)\savailable)|(Unable to find a match:\s+(.*))|(Error unpacking rpm package\s+(.*))"
    try:
        matches = re.finditer(pattern, stderr_str_info)
        for match in matches:
            if match.group(1):
                conflicts_info = match.group(1).strip().replace("\n","")
                conflicts_pkg = match.group(4).strip().replace("\n","")
                conflicts_mgs = f"[Yum Install Err]: {conflicts_info}\n[Resolve  Advice]: remove {conflicts_pkg}\n"
                WriteData(install_log_file, conflicts_mgs, mode="a")
                print(conflicts_mgs)

            elif match.group(5):
                nomatch_info = match.group(5).strip().replace("\n","")
                nomatch_pkg = match.group(6).strip().replace("\n","")
                nomatch_msg = f"[Yum Install Err]: {nomatch_info}\n[Resolve  Advice]: Check pkg '{nomatch_pkg}' in YumRepo\n"
                WriteData(install_log_file, nomatch_msg, mode="a")
                print(nomatch_msg)

            elif match.group(7):
                noackage_info = match.group(7).strip().replace("\n","")
                noackage_pkg = match.group(8).strip().replace("\n","")
                noackage_msg = f"[Yum Install Err]: {noackage_info}\n[Resolve  Advice]: Check pkg '{noackage_pkg}' in YumRepo\n"
                WriteData(install_log_file, noackage_msg, mode="a")
                print(noackage_msg)

            elif match.group(9):
                unable_info = match.group(9).strip().replace("\n","")
                unable_pkg = match.group(10).strip().replace("\n","")
                unable_msg = f"[Yum Install Err]: {unable_info}\n[Resolve  Advice]: Check pkg '{unable_pkg}' in YumRepo\n"
                WriteData(install_log_file, unable_msg, mode="a")
                print(unable_msg)

            elif match.group(11):
                unpack_info = match.group(11).strip().replace("\n","")
                unpack_pkg = match.group(12).strip().replace("\n","")
                unpack_msg = f"[Yum Install Err]: {unpack_info}\n[Resolve  Advice]: Check whether software package '{unpack_pkg}' is compatible with OpenCloudOS 9\n"
                WriteData(install_log_file, unpack_msg, mode="a")
                print(unpack_msg)
    except Exception as e:
        pass


def WriteData(write_file, write_msg, mode="w"):
    ''' Write info to file '''
    file_object = ""
    with open(write_file, mode) as file_object:
        file_object.write(write_msg)


def ServiceCheck():
    ''' Check service, if it is fail,will try to build or start it '''
    for service_name, pkg_name in services.items():
        status_result = subprocess.run(f"systemctl status {service_name}",shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE,check=False)
        if status_result.returncode != 0:
            print(f"INFO: Check the package of {pkg_name} for service {service_name}.service")
            install_result = subprocess.run(f"yum install -y {pkg_name}" ,shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE,check=False)
            if install_result.returncode != 0:
                print(f"[FAIL]: Fail to install {pkg_name} for service of {service_name}.service!")
                continue
